package com.jt.aop;

import com.jt.annotation.CacheFind;
import com.jt.util.ObjectMapperUtil;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisCluster;
import redis.clients.jedis.JedisSentinelPool;
import redis.clients.jedis.ShardedJedis;
import redis.clients.jedis.params.SetParams;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

@Aspect     //标识切面
@Component  //将对象交给Spring容器管理
@Slf4j
public class CacheAop {
    @Autowired
    private Jedis jedis;//单台
    //private ShardedJedis jedis;
    //private JedisSentinelPool sentinelPool;
    //private JedisCluster jedis;

    /**
     * 实现思路:
     *      1.动态获取key  用户自定义前缀+用户的参数
     *      2.判断key是否
     *          存在,从redis中获取数据(Json)->需要将Json转化为具体的对象(按照返回值的类型)
     *          不存在,执行目标方法.获得目标方法返回值的数据     需要将返回值的结果转化为Json格式  存到redis中
     */
    @Around(value = "@annotation(cacheFind)")//如果只是自己用,就不需要写方法.如果看不懂这句话去看看125行
    public Object around(ProceedingJoinPoint joinPoint,CacheFind cacheFind){//编译的时候,由于方法参数CacheFind已经引入了,所以在表达式就不需要再引入了只写一个方法参数名就可以.运行期间,表达式里还是注解的全类名
        // get jedis from pool
        //Jedis jedis = sentinelPool.getResource();
        Object result = null;
        //1.获取key的前缀    ITEM_CAT_PARENT
        String key = cacheFind.key();
        //2.获取方法参数,并拼接key   ITEM_CAT_PARENT::[24]
        String argString = Arrays.toString(joinPoint.getArgs());
        key += "::"+argString;
        try {
            //3.判断缓存中是否有数据
            if (jedis.exists(key)){//存在  直接获取缓存  转化为对象
                String json = jedis.get(key);
                //5.获取返回值类型
                MethodSignature signature = (MethodSignature) joinPoint.getSignature();//父转子,向下造型,要强转
                Class returnType = signature.getReturnType();
                result = ObjectMapperUtil.toObject(json, returnType);
                log.info("AOP查询redis缓存");
            }else {//不存在  应该先查询数据库  将数据保存到缓存中
                result = joinPoint.proceed();
                String json = ObjectMapperUtil.toJson(result);
                //4.判断是否有超时时间
                if (cacheFind.seconds()>0)
                    jedis.setex(key, cacheFind.seconds(), json);
                else
                    jedis.set(key, json);
                log.info("AOP执行数据库操作");
            }
        } catch (Throwable throwable) {
            throwable.printStackTrace();
            throw new RuntimeException(throwable);
        }
        //归还jedis到pool中
        //jedis.close();
        return result;
    }
//----------------------------------------------可爱的分割线,以下为aop知识点-----------------------------------------------------------------
    /**
     * AOP的一些知识点:
     * 切面=切入点表达式 + 通知方法
     * 表达式1:只拦截ItemCatServiceImpl类 "bean(itemCatServiceImpl)"
     * @Pointcut("bean(itemCatServiceImpl)")
     * @Pointcut("within(com.jt.serive.*)")
     * .*表示包下的所有类  ..*表示包下以及其子孙所有的类
     * 返回值类型任意 com.jt.service包的所有类的方法名为add的 参数为任意类型的 方法
     * 写参数类型时注意大小写,包装类Long和基本类型long
     */
    /*@Pointcut("execution(* com.jt.service..*.*(..))")
    public void pointcut(){}*/

    /**除了around之外的四种通知类型主要是用于监控目标方法的执行,方法参数是joinPoint
     * joinPoint 代表连接点对象 <- 程序执行的目标方法进入了切面
     *                路人           客人
     */
    /*@Before("pointcut()")
    public void before(JoinPoint joinPoint){
        //1.获取目标对象
            Object target = joinPoint.getTarget();
            System.out.println(target);
        //2.获取目标对象的路径  包名.类名.方法名
            String className = joinPoint.getSignature().getDeclaringTypeName();
            String methodName = joinPoint.getSignature().getName();
            System.out.println("目标方法的路径:"+className+"."+methodName);
        //3.获取参数的类型
            System.out.println(Arrays.toString(joinPoint.getArgs()));
    }*/

    /**
     * Around可以控制目标方法的执行,此时方法参数是ProceedingJoinPoint
     */
    /*@Around("pointcut()")
    public Object around(ProceedingJoinPoint joinPoint){
        Object data = null;
        try {
            data = joinPoint.proceed();
        } catch (Throwable throwable) {
            throwable.printStackTrace();
        }
        return data;
    }*/

    /**
     * 如果说别人也用你这个切入点表达式,可以写个方法(128行).如果就是自己用,就可以不写方法.在@Around()里直接写切入点表达式
     */
    /*@Pointcut("@annotation(com.jt.annotation.CacheFind)")// && args(key,seconds)
    public void cacheFind(){}   //String key,int seconds
    @Around("cacheFind()")      //如果是多个人用,可以用一个方法封装表达式
    public Object around(ProceedingJoinPoint joinPoint,CacheFind cacheFind){}*/

}
